package com.sl.core.engine.trans;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.sl.core.engine.ProcessConfig;
import com.sl.core.engine.meta.trans.ProcessTransDescriptor;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;

import java.util.Date;

@Slf4j
public class ProcessInstancePool {

    public static final ProcessInstancePool INSTANCE = new ProcessInstancePool();
    @Getter
    private final GenericKeyedObjectPool<ProcessInstancePoolKey, ProcessInstance> pool;

    public ProcessInstancePool() {
        GenericKeyedObjectPoolConfig<ProcessInstance> config = new GenericKeyedObjectPoolConfig<>();
        // 最大资源数
        config.setMaxTotal(100000);
        // 每个 key 对应的最大资源数
        config.setMaxTotalPerKey(40000);
        // 最大 key 数量
        config.setMaxIdlePerKey(30000);
        // 没有可用资源时是否要等待
        config.setBlockWhenExhausted(true);
        // 创建新资源对象后是否立即校验是否可用
        config.setTestOnCreate(false);
        // 借用资源对象前是否校验是否可用
        config.setTestOnBorrow(true);

        KeyedPooledObjectFactory<ProcessInstancePoolKey, ProcessInstance> factory = new ProcessInstanceObjectFactory();
        this.pool = new GenericKeyedObjectPool<>(factory, config);
    }

    @SneakyThrows
    public ProcessInstance getObj(ProcessTransDescriptor transMeta, ProcessConfig processConfig) {
        ProcessInstancePoolKey s = BeanUtil.copyProperties(processConfig, ProcessInstancePoolKey.class);
        s.setProcessTransDescriptor(transMeta);
        ProcessInstance processInstance = null;
        String openId = processConfig.getOpenId();
        try {
            if (StrUtil.isNotBlank(openId)) {
                processInstance = this.pool.borrowObject(s);
            }
            if (null == processInstance) {
                processInstance = new ProcessInstance(transMeta, s);
                processInstance.prepareExecution();
            } else {
                processInstance.setLogLevel(processConfig.getLogLevelCode());
            }
        } catch (Exception e) {
            log.error("获取对象失败", e);
//            ProcessTransDescriptor transMeta = ProcessRepositoryProcessManager.INSTANCE.getTransMeta(s.getOpenId(), s);
            processInstance = new ProcessInstance(transMeta, s);
            processInstance.prepareExecution();

        }
        //设置tid
        processInstance.setTid(processConfig.getTid());
        processInstance.setInstanceId(processConfig.getInstanceId());
        processInstance.setExecutionStartDate(processConfig.getStartTime());
        processInstance.setFinishEnduranceLogFlag(false);
        processInstance.setCompensateFlag(processConfig.getCompensateFlag());
        processInstance.setCompensateLogId(processConfig.getCompensateLogId());
        return processInstance;
    }

    public void returnObj(ProcessTransDescriptor transMeta, ProcessConfig processConfig, ProcessInstance trans) {
        String openId = processConfig.getOpenId();
        if (StrUtil.isBlank(openId)) {
            return;
        }
        ProcessInstancePoolKey s = BeanUtil.copyProperties(processConfig, ProcessInstancePoolKey.class);
        s.setProcessTransDescriptor(transMeta);
        this.pool.returnObject(s, trans);
    }

    public void clear(String id) {
        ProcessInstancePoolKey processInstancePoolKey = new ProcessInstancePoolKey();
        processInstancePoolKey.setOpenId(id);
        this.pool.clear();
    }

    public void clear() {
        this.pool.clear();
    }


    public class ProcessInstanceObjectFactory implements KeyedPooledObjectFactory<ProcessInstancePoolKey, ProcessInstance> {
        @Override
        public void activateObject(ProcessInstancePoolKey s, PooledObject<ProcessInstance> pooledObject) throws Exception {
            ProcessInstance object = pooledObject.getObject();
        }

        @Override
        public void destroyObject(ProcessInstancePoolKey s, PooledObject<ProcessInstance> pooledObject) throws Exception {
            ProcessInstance object = pooledObject.getObject();
            object.cleanup();
            object.safeStop();
            object.stopAll();
        }

        @Override
        public PooledObject<ProcessInstance> makeObject(ProcessInstancePoolKey s) throws Exception {
            String openId = s.getOpenId();
            if (StrUtil.isBlank(openId)) {
                return new DefaultPooledObject<>(null);
            }
            ProcessTransDescriptor transMeta = s.getProcessTransDescriptor();
            ProcessInstance trans = new ProcessInstance(transMeta, s);
            trans.setTid(s.getTid());
            trans.setInstanceId(s.getInstanceId());

            //预执行不要保存日志
//            trans.setFinishEnduranceLogFlag(true);
            trans.prepareExecution();
            return new DefaultPooledObject(trans);
        }

        @Override
        public void passivateObject(ProcessInstancePoolKey s, PooledObject<ProcessInstance> pooledObject) throws Exception {
            ProcessInstance object = pooledObject.getObject();
            object.safeStop();
            object.stopAll();
            object.cleanup();
            //设置instanceId
            object.setInstanceId(null);
            object.setTid(null);
            object.setCompensateFlag(false);
            object.removeAllListener();
            //预执行不能保存日志
//            object.setFinishEnduranceLogFlag(true);
            object.prepareExecution();
        }

        @Override
        public boolean validateObject(ProcessInstancePoolKey s, PooledObject<ProcessInstance> pooledObject) {
            return true;
        }
    }

    @Getter
    @Setter
    public static class ProcessInstancePoolKey extends ProcessConfig {

        private ProcessTransDescriptor processTransDescriptor;

        @Override
        public int hashCode() {
            String openId = this.getOpenId();
            if (StrUtil.isBlank(openId)) {
                return super.hashCode();
            }
            return openId.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (null == o) {
                return false;
            }

            if (!(o instanceof ProcessInstancePoolKey c)) {
                return false;
            }
            String openId = c.getOpenId();
            return StrUtil.equals(openId, this.getOpenId());
        }
    }
}
